/* eslint-disable no-bitwise */
/**
 * 用户模型
 *
 * Created by snowingsea on 2020/01/21.
 */
const fs = require('fs-extra');
const crypto = require('crypto');
const randomstring = require('randomstring');
const mongoose = require('mongoose');
const moment = require('moment');
const escapeRegexp = require('escape-regexp');
const { default: fetch } = require('node-fetch');
const Token = require('./mongo/token');
const User = require('./mongo/user');
const Heartbeat = require('./mongo/heartbeat');
const publisher = require('./redis/publisher');
const Filter = require('./mongo/filter');
const gitClient = require('./git/git-client');
const encrypt = require('./encrypt');
const utility = require('./utility');
const { eventHub, eventKind } = require('./event-hub');

/**
 * 用户角色类型
 * @type {Object}
 */
const userRoleKind = {
  superUser: 1 << 0, // 1 超级用户
  administrator: 1 << 1, // 2 管理员
  tester: 1 << 2, // 4 测试人员
  testManager: 1 << 3, // 8 测试经理
};

/**
 * 用户状态
 * @type {Object}
 */
const userStatusKind = {
  deleting: -1, // 正在删除
  unactivated: 0, // 未激活
  offline: 1, // 离线
  online: 2, // 在线
};
/**
 * 用户来源
 * @type {Object}
 */
const userSource = {
  testGrid: 1,
  qst: 2, // 青软用户
  educoder: 3, // 头哥用户
};
/**
 * 获取用户json信息
 * @param {User} user 用户信息
 * @return {Object}
 */
function getJson(user) {
  return {
    userId: user._id, // eslint-disable-line no-underscore-dangle
    username: user.username,
    nickname: user.nickname,
    status: user.status,
    role: user.role,
    lang: user.lang,
    source: user.source ? user.source : userSource.testGrid,
    userHash: user.userHash || '',
  };
}

async function getSummaryJson(user) {
  user.userHash = await utility.getHash();
  return getJson(user);
}

/**
 * 获取用户
 * @param {String} userId 用户id
 * @return {Promise}
 */
function getUser(userId) {
  return User.findOne({ _id: mongoose.Types.ObjectId(userId) });
}

/**
 * 通过用户名获取用户信息
 * @param {String} username 用户名
 * @return {Promise}
 */
function getUserByName(username) {
  return User.findOne({ username });
}

/**
 * 获取用户集合
 * @param {Array} userIds 用户id列表
 * @param {Array} exceptUserIds 不计入的用户id列表
 * @param {Object} options 选项
 * @returns {Promise}
 */
function getUsers({ userIds = null, exceptUserIds = null }, options = {}) {
  if (userIds) {
    if (!utility.isArrayType(userIds)) {
      userIds = [userIds];
    }
    userIds = userIds.map((userId) => mongoose.Types.ObjectId(userId));
    options._id = { $in: userIds }; // eslint-disable-line no-underscore-dangle
  } else {
    if (!exceptUserIds) {
      exceptUserIds = [];
    }
    if (!utility.isArrayType(exceptUserIds)) {
      exceptUserIds = [exceptUserIds];
    }
    exceptUserIds = exceptUserIds.map((userId) => mongoose.Types.ObjectId(userId));
    options._id = { $nin: exceptUserIds }; // eslint-disable-line no-underscore-dangle
  }
  return User.find(options);
}

/**
 * 获取用户列表请求约束
 * @param {Array|Number} status 用户状态
 * @param {Array|Number} roles 用户角色
 * @param {String} q 搜索关键词
 * @return {Object}
 */
function getUserOptions({ status = null, roles = null, q = null }) {
  const options = {};
  // 用户状态
  if (status) {
    if (!utility.isArrayType(status)) {
      status = [status];
    }
    const statusSet = new Set(status.map((status) => Number.parseInt(status.toString(), 10)));
    if (statusSet.has(userStatusKind.offline)) {
      statusSet.add(userStatusKind.unactivated);
    }
    options.status = { $in: [...statusSet] };
  }
  // 用户角色
  let allRole = 0;
  Object.keys(userRoleKind).forEach((key) => {
    allRole |= userRoleKind[key]; // eslint-disable-line no-bitwise
  });
  if (roles) {
    const inRoles = [];
    if (!utility.isArrayType(roles)) {
      roles = [roles];
    }
    let queryRole = 0;
    roles.forEach((itemRole) => {
      queryRole |= Number.parseInt(itemRole.toString(), 10); // eslint-disable-line no-bitwise
    });
    for (let role = 1; role <= allRole; role += 1) {
      if ((role & userRoleKind.superUser) === 0 // eslint-disable-line no-bitwise
        && (queryRole & role) > 0) { // eslint-disable-line no-bitwise
        inRoles.push(role);
      }
    }
    options.role = { $in: inRoles };
  } else {
    const ninRoles = [];
    for (let role = 1; role <= allRole; role += 1) {
      if ((role & userRoleKind.superUser) > 0) { // eslint-disable-line no-bitwise
        ninRoles.push(role);
      }
    }
    options.role = { $nin: ninRoles };
  }
  // 关键词搜索
  if (q) {
    options.$or = [{
      username: { $regex: `.*${escapeRegexp(q)}.*` },
    }];
  }
  return options;
}

async function getUserList({
  q = null,
  roles = null,
  page = 1,
  pageSize = 10,
  sortBy = '_id',
  order = 'asc',
  status = null,
}) {
  const exec = User.aggregate([
    {
      $lookup: {
        from: 'heartbeats', localField: '_id', foreignField: 'userId', as: 'heartbeat',
      },
    },
    {
      $set: {
        // 用心跳判断是否在线
        alive: { $toBool: { $size: '$heartbeat' } },
      },
    },
    {
      $set: {
        // 剔除掉心跳超时但是user表里状态还是online的用户
        status: {
          $cond: {
            if: {
              $and: [
                { $eq: ['$status', userStatusKind.online] },
                { $eq: ['$alive', false] },
              ],
            },
            then: userStatusKind.offline,
            else: '$status',
          },
        },
      },
    },
  ]);
  // 过滤条件
  exec.append({
    $match: getUserOptions({
      status,
      roles,
      q,
    }),
  });
  // 查询总数
  const countRet = await User.aggregate(exec.pipeline()).append({ $count: 'total' }).exec();
  const [{ total }] = countRet && countRet.length ? countRet : [{ total: 0 }];

  if (!sortBy || (sortBy === 'userId') || (sortBy === 'createAt')) {
    sortBy = '_id';
  }

  exec.append([
    {
      $sort: {
        [sortBy]: order.toLowerCase() === 'asc' ? 1 : -1,
      },
    },
    {
      $skip: (page - 1) * pageSize,
    },
    {
      $limit: pageSize,
    },
  ]);

  const users = await exec.exec();
  return {
    total,
    users,
  };
}

async function resetAllUserStatus() {
  await User.updateMany({
    status: userStatusKind.online,
  }, {
    $set: {
      status: userStatusKind.offline,
    },
  });
  await Heartbeat.deleteMany({});
}

/**
 * 筛选用户列表
 * @param {Object} pagination 分页信息
 * @param {Object} options 用户筛选项
 * @return {Promise}
 */
function filter(pagination = {}, options = {}) {
  const { sortBy, collation } = pagination;
  if (sortBy === 'username' && !collation) {
    pagination.collation = { locale: 'en', caseFirst: 'lower', numericOrdering: true };
  }
  if (!sortBy || (sortBy === 'userId') || (sortBy === 'createAt')) {
    pagination.sortBy = '_id';
  }
  return Filter.filter(User, pagination, options);
}

/**
 * 用户数目
 * @param {Object} options 用户筛选项
 * @return {Promise}
 */
function getCount(options = {}) {
  return User.countDocuments(options);
}

/**
 * 获取用户分组统计
 * @param {String} key 要分组的字段名
 * @return {Promise}
 */
function getGroupCount(key) {
  let allRole = 0;
  Object.keys(userRoleKind).forEach((key) => {
    allRole |= userRoleKind[key]; // eslint-disable-line no-bitwise
  });
  const inRoles = [];
  for (let role = 0; role <= allRole; role += 1) {
    if ((role & userRoleKind.superUser) === 0) { // eslint-disable-line no-bitwise
      inRoles.push(role);
    }
  }

  return User.aggregate([{
    $match: {
      role: { $in: inRoles },
    },
  }, {
    $group: {
      _id: `$${key}`,
      count: { $sum: 1 },
    },
  }]);
}

/**
 * 获取测试人员数量
 * @return {Promise}
 */
function getTesterCount() {
  return new Promise((resolve, reject) => {
    getGroupCount('role')
      .then((res) => {
        let testerCount = 0;
        res.forEach(({ _id, count }) => {
          if ((_id & userRoleKind.tester) > 0) { // eslint-disable-line no-bitwise
            testerCount += count;
          }
        });
        resolve(testerCount);
      })
      .catch(reject);
  });
}

/**
 * 添加用户
 * @param {String} username 用户名
 * @param {String} password 用户密码
 * @param {Array|Number} roles 角色
 * @param {String} nickname 昵称（真实姓名）
 * @param {String} lang 用户当前使用语言
 * @return {Promise}
 */
function addUser(username, password, roles = 0, nickname = null, lang = 'zh-CN') {
  return new Promise((resolve, reject) => {
    encrypt.encrypt(password.toLowerCase())
      .then((res) => {
        let role = 0;
        if (utility.isArrayType(roles)) {
          roles.forEach((r) => {
            role |= Number.parseInt(r, 10); // eslint-disable-line no-bitwise
          });
        } else {
          role = Number.parseInt(roles.toString(), 10);
        }
        const user = new User({
          username,
          nickname: nickname || username,
          password: res.hash,
          salt: res.salt,
          role,
          lang,
          gitPassword: randomstring.generate(16),
        });
        return user.save();
      })
      .then((user) => {
        publisher.publishUserMessage('add', getJson(user));
        resolve(user);
      })
      .catch(reject);
  });
}

/**
 * 添加样例管理员账号
 * @return {Promise}
 */
function addSampleManager() {
  return new Promise((resolve, reject) => {
    const {
      username,
      password,
      role,
      lang,
      nickname,
    } = global.config.sampleManager;
    const newPassword = crypto.createHash('sha256').update(password).digest('hex');
    addUser(username, newPassword, role, lang, nickname)
      .then(resolve)
      .catch((err) => {
        err.code === 11000 ? resolve() : reject(err);
      });
  });
}
/**
 * 添加默认测试人员账号
 * @return {Promise}
 */
function addSampleUsers() {
  if (!global.config.sampleUsers || global.config.sampleUsers.length === 0) {
    return Promise.resolve();
  }
  return Promise.all(global.config.sampleUsers.map(async ({
    username,
    password,
    role,
  }) => {
    const newPassword = crypto.createHash('sha256').update(password).digest('hex');
    try {
      await addUser(username, newPassword, role || userRoleKind.tester);
    } catch (err) {
      err.code === 11000 ? Promise.resolve() : Promise.reject(err);
    }
  }));
}

/**
 * 更新用户信息
 * @param {String} userId 用户Id
 * @param {String} username 用户名称
 * @param {String} password 用户密码
 * @param {String} nickname 昵称（真实姓名）
 * @param {Array|Number} roles 用户角色
 * @param {Number} status 用户状态
 * @param {String} lang 用户当前使用语言
 * @return {Promise}
 */
function updateUser(userId, {
  username = null,
  password = null,
  nickname = null,
  roles = null,
  status = null,
  lang = null // eslint-disable-line comma-dangle
}) {
  const info = {};
  if (username) {
    info.username = username;
  }
  if (nickname) info.nickname = nickname;
  if (roles) {
    let role = 0;
    if (utility.isArrayType(roles)) {
      roles.forEach((r) => {
        role |= Number.parseInt(r, 10); // eslint-disable-line no-bitwise
      });
    } else {
      role = Number.parseInt(roles.toString(), 10);
    }
    info.role = role;
  }
  if (status) info.status = status;
  if (lang) info.lang = lang;
  return new Promise((resolve, reject) => {
    let pwdPromise;
    if (password) {
      pwdPromise = new Promise((resolve, reject) => {
        encrypt.encrypt(password.toLowerCase())
          .then((res) => {
            info.password = res.hash;
            info.salt = res.salt;
            resolve();
          })
          .catch(reject);
      });
    } else {
      pwdPromise = Promise.resolve();
    }

    pwdPromise
      .then(() => User.updateOne({ _id: mongoose.Types.ObjectId(userId) }, { $set: info }))
      .then((user) => {
        delete info.password;
        delete info.salt;
        // 延时的目的是 防止用户列表刷新时用户会错过自己重连时的登录状态消息导致看自己的状态永远为离线
        setTimeout(() => {
          publisher.publishUserMessage('update', info);
        }, 500);
        resolve(user);
      })
      .catch(reject);
  });
}

/**
 * 更新用户访问时间
 * @param {String} userId 用户Id
 * @return {Promise}
 */
function updateUserVisitTime(userId) {
  const doc = {
    lastVisitAt: moment(),
    $inc: { visitCount: 1 },
  };
  return User.updateOne({ _id: mongoose.Types.ObjectId(userId) }, doc);
}

/**
 * 删除用户
 * @param {String} userId 用户Id
 * @return {Promise}
 */
function removeUser(userId) {
  return new Promise((resolve, reject) => {
    userId = mongoose.Types.ObjectId(userId);
    updateUser(userId, { status: userStatusKind.deleting })
      .then(() => {
        // 踢出在线用户
        publisher.publishLogoutMessage(userId);
        return Token.deleteMany({ userId });
      })
      .then(() => eventHub.emit(eventKind.EVENT_REMOVE_USER, userId))
      .then(() => getUser(userId))
      .then((user) => fs.remove(gitClient.getLocalUserPath(user)))
      .then(() => User.deleteOne({ _id: userId }))
      .then((res) => {
        publisher.publishUserMessage('delete', { userId });
        resolve(res);
      })
      .catch(reject);
  });
}
/**
 * 获取qst用户信息
 * @param {String} code qst 验证平台code
 */
async function getQstUser(code) {
  if (!global.config.thirdPartySystem || !global.config.thirdPartySystem.qst) {
    throw new Error('missing config. qst "config" is required');
  }
  const {
    clientId,
    clientSecret,
    redirectUri,
    baseUrl,
    apiVersion,
  } = global.config.thirdPartySystem.qst;
  if (!clientId) {
    throw new Error('missing config. qst "clientId" is required');
  }
  if (!clientSecret) {
    throw new Error('missing config. qst "clientSecret" is required');
  }
  if (!redirectUri) {
    throw new Error('missing config. qst "redirectUri" is required');
  }
  if (!baseUrl) {
    throw new Error('missing config. qst api "baseUrl" is required');
  }
  if (!apiVersion) {
    throw new Error('missing config. qst "apiVersion" is required');
  }

  const qstTokenUrl = `${baseUrl}/${apiVersion}/oauth2/token`;
  const qstUserInfoUrl = `${baseUrl}/${apiVersion}/oauth2/userinfo`;
  // 获取qst token
  const qstToken = await (await fetch(qstTokenUrl, {
    method: 'POST',
    body: JSON.stringify({
      clientId,
      clientSecret,
      redirectUri,
      grantType: 'authorization_code',
      code,
    }),
    headers: { 'Content-Type': 'application/json' },
    timeout: 10000,
  })).json();
  if (!qstToken || qstToken.status !== 200 || !qstToken.data || !qstToken.data.accessToken) {
    throw new Error(`get qst token failed :${JSON.stringify(qstToken)}`);
  }
  // 获取qst 用户信息
  const qstUser = await (await fetch(qstUserInfoUrl, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${qstToken.data.accessToken}`,
    },
    timeout: 10000,
  })).json();
  if (!qstUser || qstUser.status !== 200 || !qstUser.data || !qstUser.data.username || !qstUser.data.userid) {
    throw new Error(`get qst user failed :${JSON.stringify(qstToken)}`);
  }
  const newQstUserName = `${qstUser.data.username}_qst`;
  const testGridUser = await User.findOneAndUpdate(
    {

      sourceUserid: qstUser.data.userid,
      source: userSource.qst,
    },
    {
      $set: {
        username: newQstUserName,
        source: userSource.qst,
        sourceUserid: qstUser.data.userid,
        role: userRoleKind.tester,
        status: userStatusKind.online,
        lang: 'zh-CN',
        nickname: newQstUserName,
      },
    },
    {
      upsert: true,
      new: true,
    }
  );
  return testGridUser;
}
/**
 * 获取头哥用户信息
 * @param {*} username
 */
async function geteducoderUser(userName) {
  const newUserName = `${userName}_educoder`;
  const testGridUser = await User.findOneAndUpdate(
    {

      username: newUserName,
      source: userSource.educoder,
    },
    {
      $set: {
        username: newUserName,
        source: userSource.educoder,
        sourceUserid: '',
        role: userRoleKind.tester,
        status: userStatusKind.online,
        lang: 'zh-CN',
        nickname: newUserName,
      },
    },
    {
      upsert: true,
      new: true,
    }
  );
  return testGridUser;
}
/**
 * Export model definition object.
 */
module.exports = {
  roleKind: userRoleKind,
  statusKind: userStatusKind,
  userSource,

  getJson,
  getSummaryJson,

  getUser,
  getUserByName,
  getUsers,
  getUserOptions,
  filter,
  getCount,
  getGroupCount,
  getTesterCount,
  getQstUser,
  geteducoderUser,

  updateUser,
  updateUserVisitTime,
  removeUser,
  addUser,

  addSampleManager,
  addSampleUsers,

  getUserList,

  resetAllUserStatus,
};
exports = Object.assign(exports, module.exports);
